package cn.com.wxd.amumu.search.core;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.LockObtainFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hankcs.lucene.HanLPAnalyzer;
import cn.com.wxd.amumu.search.entity.IndexEntity;

/**
 * @author WangXuDong
 *         索引新建与操作类
 * @version 1.0
 */
public class IndexFile {
    private static final Logger log = LoggerFactory.getLogger(IndexFile.class);
    private Directory dir;

    /**
     * 新建索引
     *
     * @param indexEntitylist 索引列表
     * @param filePath        文件地址
     *
     * @throws Exception 文件读取错误
     */
    public void buildIndex(List<IndexEntity> indexEntitylist, String filePath) throws Exception {
        //创建Directory对象有多种方式，使用FSDirectory.open()创建它会选择最优的方式获得
        //Directory对象，Directory实现类包括SimpleFSDirectory和RAMDirectory两个类
        //其中RAMDirectory是在内存中创建索引对象
        //1、创建Directory对象来保存索引
        dir = FSDirectory.open(Paths.get(filePath));
        IndexWriter writer = getWriter();
        for (int i = 0; i < indexEntitylist.size(); i++) {
            //3、创建Document对象
            Document doc = new Document();
            /**
             * 对于那种已经有明确的类型的字段可以设置成 IntField、FloatField、LongField等，
             * 这种字段类型就像API中说的一样，可以高效的处理过滤和排序。
             * 如果索引文件中，某些字段是用来排序或者索引的，那这种类型应该是不错的选择。
             * StringField 是将属性值当做一个整体，并不对其做分词操作，但是可以做检索。
             * StoredField 只是将属性值做简单的存储，并不参与搜索、过滤和排序等操作，
             * 只是在查询索引时能通过IndexSearcher.doc(int) 或 IndexReader.document(
             * int, org.apache.lucene.index.StoredFieldVisitor)  取得对应的字段值。
             * TextField 类型主要用于根据用户输入词进行搜索的字段，这里就牵扯到分词器的选择。
             */
            //doc.add(new StoredField("id",articleInfolist.get(i).getId()));
            //查处结果和内容，但不参与分词
            doc.add(new StringField("id", String.valueOf(indexEntitylist.get(i).getId()), Store.YES));
            //只负责查询内容不查处结果,Store.NO
            doc.add(new TextField("content", indexEntitylist.get(i).getContent(), Store.NO));
            HashMap<String, String> map = indexEntitylist.get(i).getOtherParam();

            //指定其他的参数
            if (map != null) {
                Iterator it = map.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry entry = (Map.Entry) it.next();
                    String key = (String) entry.getKey();
                    String value = (String) entry.getValue();
                    if (key != null && !key.equals("")) {
                        if (value != null && !value.equals("")) {
                            doc.add(new TextField(key, value, Store.YES));
                        }
                    }
                }
            }
            try {
                writer.addDocument(doc);
            } catch (Exception e) {
                log.error("建立索引异常" + e.getMessage(), e);
                writer.close();
                throw e;
            }
        }
        writer.close();
    }

    /**
     * 删除索引
     *
     * @return
     */
    public boolean deleteIndex(String[] indexId, String filePath) throws Exception {
        IndexWriter writer = getWriter();
        try {
            dir = FSDirectory.open(Paths.get(filePath));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return false;
        }
        try {
            //参数十一个选项，可以是一个query，也可以是一个term  term就是一个精确查找的值  
            //此时删除的文档并未完全删除，而是存储在回收站中，可以恢复的  
            for (int i = 0; i < indexId.length; i++) {
                writer.deleteDocuments(new Term("id", indexId[i]));
            }
            return true;
        } catch (CorruptIndexException e) {
            log.error("删除索引发生错误：" + e.getMessage(), e);
            return false;
        } catch (LockObtainFailedException e) {
            log.error("删除索引发生错误：" + e.getMessage(), e);
            return false;
        } catch (IOException e) {
            log.error("删除索引发生错误：" + e.getMessage(), e);
            return false;
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (CorruptIndexException e) {
                    if (log.isErrorEnabled()) {
                        log.error(e.getMessage(), e);
                    }
                    return false;
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                    return false;
                }
            }
        }
    }

    /**
     * 清空回收站（消耗内存较大，不推荐使用）
     *
     * @param filePath
     */
    public void forceDelete(String filePath) throws Exception {
        try {
            dir = FSDirectory.open(Paths.get(filePath));
        } catch (IOException e1) {
            log.error(e1.getMessage(), e1);
        }
        IndexWriter writer = getWriter();
        try {
            writer.forceMergeDeletes();
        } catch (IOException e) {
            log.error("清空索引回收站错误：" + e.getMessage(), e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 强制优化索引，（会删除回收站的数据）
     *
     * @param filePath
     *
     * @throws Exception
     */
    public void merge(String filePath) throws Exception {
        try {
            dir = FSDirectory.open(Paths.get(filePath));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        IndexWriter writer = getWriter();
        try {
            writer.forceMerge(2);
        } catch (IOException e) {
            log.error("强制优化索引错误：" + e.getMessage(), e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 更新索引
     */
    public void update(IndexEntity indexEntitylist, String filePath) throws Exception {
        /*lucene本身不支持更新 
         *  
         * 通过删除索引然后再建立索引来更新 
         */
        if (indexEntitylist != null && indexEntitylist.getId() != null && indexEntitylist.getId() != "") {
            try {
                deleteIndex(new String[] {indexEntitylist.getId()}, filePath);
                List<IndexEntity> list = new ArrayList<>();
                list.add(indexEntitylist);
                buildIndex(list, filePath);
            } catch (Exception e) {
                log.error("更新索引错误：" + e.getMessage(), e);
            }
        }
    }

    private IndexWriter getWriter() throws Exception {
        //2、创建IndexWriter对象需要IndexWriterConfig和Analyzer对象，这里使用标准分词器
        //Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_44);
        //庖丁解牛分词器
        //Analyzer analyzer2 = new PaodingAnalyzer();
        //使用IK分词器，效果好
        //Analyzer analyzer = new IKAnalyzer();
        //使用自然语言处理分词器比较流行
        Analyzer analyzer = new HanLPAnalyzer();
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        return new IndexWriter(dir, config);
    }
}
